package main

import (
	"fmt"

	"io"
	"time"

	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/sirupsen/logrus"
	//配置
	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"github.com/spf13/viper"

	"github.com/gohouse/gorose/v2"

	_ "github.com/vertica/vertica-sql-go"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/config"
	"github.com/go-git/go-git/v5/plumbing/object"
	"github.com/go-git/go-git/v5/plumbing/transport/http"

	"github.com/robfig/cron"
	"sync"
)

var LOG = logrus.New() // 创建一个log示例

func InitLogrus() (err error) { // 初始化log的函数
	LOG.Formatter = &logrus.JSONFormatter{
		FieldMap: logrus.FieldMap{
			"FieldKeyTime":  "@timestamp",
			"FieldKeyLevel": "@level",
			"FieldKeyMsg":   "@message",
			"FieldKeyFunc":  "@caller",
		},
	} // 设置为json格式的日志
	//日志路径
	path := viper.GetString("log.path")
	//日志分日期切割
	writer, _ := rotatelogs.New(
		path+".%Y%m%d",
		rotatelogs.WithLinkName(path),
		rotatelogs.WithMaxAge(time.Duration(24)*time.Hour),
		rotatelogs.WithRotationTime(time.Duration(24)*time.Hour),
	)

	LOG.Out = io.MultiWriter(writer, os.Stdout) // 设置log的默认文件输出

	LOG.Level = logrus.InfoLevel // 设置日志级别
	//日志显示行号和方法
	LOG.SetReportCaller(true)
	return
}

//配置初始化
func InitConfig() {
	viper.SetConfigType("properties")
	viper.SetConfigFile("config/config.properties")
	err := viper.ReadInConfig() // 查找并读取配置文件
	if err != nil {             // 处理读取配置文件的错误
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}
}

var Engin *gorose.Engin

func DB() gorose.IOrm {
	return Engin.NewOrm()
}

//初始化数据库连接
func InitDB() *gorose.Engin {

	// 全局初始化数据库,并复用
	// 这里的engin需要全局保存,可以用全局变量,也可以用单例
	// 配置&gorose.Config{}是单一数据库配置
	// 如果配置读写分离集群,则使用&gorose.ConfigCluster{}
	engin, err := gorose.Open(&gorose.Config{Driver: "vertica", Dsn: fmt.Sprintf("vertica://%s:%s@%s:%s/%s", viper.GetString("vertica.user"), viper.GetString("vertica.password"), viper.GetString("vertica.host"), viper.GetString("vertica.port"), viper.GetString("vertica.db"))})
	if err != nil {
		LOG.Fatalln(err)

	}
	Engin = engin
	return Engin
}
func main() {
	//配置初始化
	InitConfig()

	//日志初始化
	err := InitLogrus()

	if err != nil {
		panic(err)
	}

	//初始化数据库
	InitDB()

	c := cron.New() // 新建一个定时任务对象
	c.AddFunc(viper.GetString("cron"), func() {
		Start()
	}) // 给对象增加定时任务
	c.Start()
	select {}
}
func Start() {

	Scemas, err := GetScemas()

	if err != nil {
		LOG.Panic(err)
	}

	ddl_path := viper.GetString("ddl.path")
	//创建目录
	os.MkdirAll(ddl_path, os.ModePerm)

	if err != nil {
		LOG.Panic(err)
	}
	git.PlainInit(ddl_path, false)
	r, err := git.PlainOpen(ddl_path)
	if err != nil {
		LOG.Panic(err)
	}
	w, err := r.Worktree()

	if err != nil {
		LOG.Panic(err)
	}

	os.Remove(ddl_path + "/.git/index")
	head, err := r.Head()
	if err != nil {
		LOG.Panic(err)
	}

	if err := w.Reset(&git.ResetOptions{
		Mode:   git.HardReset,
		Commit: head.Hash(),
	}); err != nil {
		LOG.Panic(err)
	}

	var wg sync.WaitGroup

	for _, s := range Scemas {
		scema := s["schema_name"].(string)

		wg.Add(1)

		go func() {
			defer wg.Done()
			os.MkdirAll(ddl_path+"/"+scema+"/TABLE", os.ModePerm)
			os.MkdirAll(ddl_path+"/"+scema+"/VIEW", os.ModePerm)

			tables, err := GetTables(scema)
			if err != nil {
				LOG.Panic(err)
			}

			for _, t := range tables {
				tablename := t["table_name"].(string)

				ddl, err := GetDll(scema, tablename)
				if err != nil {
					LOG.Error(err)
				} else {
					filename := filepath.Join(ddl_path+"/"+scema+"/TABLE", tablename+".sql")
					LOG.Info(filename)
					os.Remove(filename)
					err = ioutil.WriteFile(filename, []byte(ddl), 0644)
					if err != nil {

						LOG.Panic(err)
					}
					//w.Add(scema + "/TABLE/" + tablename + ".sql")
				}

			}
		}()

		wg.Add(1)

		go func() {
			defer wg.Done()
			views, err := GetViews(scema)
			if err != nil {
				LOG.Panic(err)
			}

			for _, t := range views {
				tablename := t["table_name"].(string)

				ddl, err := GetDll(scema, tablename)
				if err != nil {
					LOG.Error(err)
				} else {
					filename := filepath.Join(ddl_path+"/"+scema+"/VIEW", tablename+".sql")
					os.Remove(filename)
					err = ioutil.WriteFile(filename, []byte(ddl), 0644)
					if err != nil {
						LOG.Panic(err)
					}
					//w.Add(scema + "/VIEW/" + tablename + ".sql")
				}

			}
		}()
	}

	wg.Wait()

	//加入到git缓存
	for _, s := range Scemas {
		scema := s["schema_name"].(string)

		w.Add(scema)
	}

	_, err = w.Commit(time.Now().Format("2006-01-02 15:04:05.000"), &git.CommitOptions{
		Author: &object.Signature{
			Name:  "go",
			Email: "go@163.com",
			When:  time.Now(),
		},
	})
	if err != nil {
		LOG.Panic(err)
	}

	r.CreateRemote(&config.RemoteConfig{
		Name: "origin",
		URLs: []string{viper.GetString("git.repo")},
	})

	err = r.Push(&git.PushOptions{
		Auth: &http.BasicAuth{
			Username: viper.GetString("git.user"),
			Password: viper.GetString("git.password"),
		},
	})
	if err != nil {
		LOG.Panic(err)
	}
}

func GetScemas() ([]gorose.Data, error) {
	//db客户端
	dbclient := DB()

	return dbclient.Query(`
	SELECT 
    schema_name 
FROM 
    v_catalog.schemata 
WHERE 
    schema_name NOT IN ('v_internal', 
                        'v_catalog', 
                        'v_monitor', 
                        'v_txtindex')
	`)
}

func GetTables(scema string) ([]gorose.Data, error) {
	//db客户端
	dbclient := DB()

	return dbclient.Query(`
	SELECT 
    table_name 
FROM 
v_catalog.all_tables
WHERE 
    schema_name =?
	and table_type ='TABLE'
	`, scema)
}

func GetViews(scema string) ([]gorose.Data, error) {
	//db客户端
	dbclient := DB()

	return dbclient.Query(`
	SELECT 
    table_name 
FROM 
v_catalog.all_tables
WHERE 
    schema_name =?
	and table_type ='VIEW'
	`, scema)
}

func GetDll(scema string, objectname string) (string, error) {
	//db客户端
	dbclient := DB()
	list, err := dbclient.Query(fmt.Sprintf(`select export_objects('','%s.%s') c`, scema, objectname))
	if err != nil {
		return "", err
	}
	return list[0]["c"].(string), nil
}
